<template>
  <div class="upload-file">
    <el-upload
      multiple
      :action="uploadFileUrl"
      :before-upload="handleBeforeUpload"
      :file-list="fileList"
      :limit="limit"
      :on-error="handleUploadError"
      :on-exceed="handleExceed"
      :on-success="handleUploadSuccess"
      :show-file-list="false"
      :headers="headers"
      class="upload-file-uploader"
      ref="fileUploadRef"
      v-if="!disabled"
    >
      <!-- 上传按钮 -->
      <el-button size="small" type="primary">选取文件</el-button>
      <!-- 上传提示 -->
      <div class="el-upload__tip" v-if="showTip">
        请上传
        <template v-if="fileSize">
          大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
        </template>
        <template v-if="fileType">
          格式为 <b style="color: #f56c6c">{{ fileType.join('/') }}</b>
        </template>
        的文件
      </div>
    </el-upload>

    <!-- 文件列表 -->
    <transition-group
      class="upload-file-list el-upload-list el-upload-list--text"
      name="el-fade-in-linear"
      tag="ul"
    >
      <li
        :key="file.url"
        class="el-upload-list__item ele-upload-list__item-content"
        v-for="(file, index) in fileList"
      >
        <el-link :href="file.url" :underline="false" target="_blank">
          <span class="el-icon-document"> {{ getFileName(file.name) }} </span>
        </el-link>
        <div class="ele-upload-list__item-content-action">
          <el-link :underline="false" @click="handleDelete(index)" type="danger" v-if="!disabled"
            >删除</el-link
          >
        </div>
      </li>
    </transition-group>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { getToken } from '@/utils/auth'
import type { UploadInstance, UploadFile, UploadRawFile } from 'element-plus'
import { ElMessage, ElLoading } from 'element-plus'

// 定义文件对象接口
interface FileItem {
  name: string
  url: string
  uid?: number
}

// 定义Props
interface Props {
  // 值
  value?: string | FileItem | FileItem[] | null
  // 数量限制
  limit?: number
  // 大小限制(MB)
  fileSize?: number
  // 文件类型, 例如['png', 'jpg', 'jpeg']
  fileType?: string[]
  // 是否显示提示
  isShowTip?: boolean
  // 禁用组件（仅查看文件）
  disabled?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  value: null,
  limit: 5,
  fileSize: 5,
  fileType: () => ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'pdf'],
  isShowTip: true,
  disabled: false,
})

// 定义Emits
const emit = defineEmits<{
  (e: 'input', value: string): void
  (e: 'update:value', value: string): void
}>()

// Refs
const number = ref(0)
const uploadList = ref<FileItem[]>([])
const fileUploadRef = ref<UploadInstance>()
const fileList = ref<FileItem[]>([])

// 上传文件服务器地址
const uploadFileUrl = import.meta.env.VITE_APP_BASE_API + '/file/upload'

// Headers
const headers = {
  Authorization: 'Bearer ' + getToken(),
}

// 是否显示提示
const showTip = computed(() => {
  return props.isShowTip && (props.fileType || props.fileSize)
})

// Watch value
watch(
  () => props.value,
  (val) => {
    if (val) {
      let temp = 1
      // 首先将值转为数组
      const list = Array.isArray(val) ? val : String(val).split(',')
      // 然后将数组转为对象数组
      fileList.value = list.map((item) => {
        if (typeof item === 'string') {
          item = { name: item, url: item }
        }
        const fileItem = item as FileItem
        fileItem.uid = fileItem.uid || new Date().getTime() + temp++
        return fileItem
      })
    } else {
      fileList.value = []
    }
  },
  { deep: true, immediate: true },
)

// 上传前校检格式和大小
const handleBeforeUpload = (file: UploadRawFile): boolean => {
  // 校检文件类型
  if (props.fileType) {
    const fileName = file.name.split('.')
    const fileExt = fileName[fileName.length - 1]
    const isTypeOk = fileExt && props.fileType.indexOf(fileExt) >= 0
    if (!isTypeOk) {
      ElMessage.error(`文件格式不正确，请上传${props.fileType.join('/')}格式文件!`)
      return false
    }
  }
  // 校检文件名是否包含特殊字符
  if (file.name.includes(',')) {
    ElMessage.error('文件名不正确，不能包含英文逗号!')
    return false
  }
  // 校检文件大小
  if (props.fileSize) {
    const isLt = file.size / 1024 / 1024 < props.fileSize
    if (!isLt) {
      ElMessage.error(`上传文件大小不能超过 ${props.fileSize} MB!`)
      return false
    }
  }
  ElLoading.service({ text: '正在上传文件，请稍候...' })
  number.value++
  return true
}

// 文件个数超出
const handleExceed = () => {
  ElMessage.error(`上传文件数量不能超过 ${props.limit} 个!`)
}

// 上传失败
const handleUploadError = () => {
  ElMessage.error('上传文件失败，请重试')
  ElLoading.service().close()
}

// 上传成功回调
const handleUploadSuccess = (res: any, file: UploadFile) => {
  if (res.code === 200) {
    uploadList.value.push({ name: res.data.url, url: res.data.url })
    uploadedSuccessfully()
  } else {
    number.value--
    ElLoading.service().close()
    ElMessage.error(res.msg)
    fileUploadRef.value?.abort(file)
    uploadedSuccessfully()
  }
}

// 删除文件
const handleDelete = (index: number) => {
  fileList.value.splice(index, 1)
  const result = listToString(fileList.value)
  emit('input', result)
  emit('update:value', result)
}

// 上传结束处理
const uploadedSuccessfully = () => {
  if (number.value > 0 && uploadList.value.length === number.value) {
    fileList.value = fileList.value.concat(uploadList.value)
    uploadList.value = []
    number.value = 0
    const result = listToString(fileList.value)
    emit('input', result)
    emit('update:value', result)
    ElLoading.service().close()
  }
}

// 获取文件名称
const getFileName = (name: string) => {
  // 如果是url那么取最后的名字 如果不是直接返回
  if (name.lastIndexOf('/') > -1) {
    return name.slice(name.lastIndexOf('/') + 1)
  } else {
    return name
  }
}

// 对象转成指定字符串分隔
const listToString = (list: FileItem[], separator?: string) => {
  let strs = ''
  separator = separator || ','
  for (const i in list) {
    const item = list[i]
    if (item) {
      strs += item.url + separator
    }
  }
  return strs != '' ? strs.substr(0, strs.length - 1) : ''
}
</script>

<style scoped lang="scss">
.upload-file-uploader {
  margin-bottom: 5px;
}
.upload-file-list .el-upload-list__item {
  border: 1px solid #e4e7ed;
  line-height: 2;
  margin-bottom: 10px;
  position: relative;
}
.upload-file-list .ele-upload-list__item-content {
  display: flex;
  justify-content: space-between;
  align-items: center;
  color: inherit;
}
.ele-upload-list__item-content-action .el-link {
  margin-right: 10px;
}
</style>
